{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练过程\n",
    "\n",
    "训练过程采用二层循环嵌套方式：\n",
    "\n",
    "- **内层循环：** 负责整个数据集的一次遍历，采用分批次方式（batch）。假设数据集样本数量为1000，一个批次有10个样本，则遍历一次数据集的批次数量是1000/10=100，即内层循环需要执行100次。\n",
    "\n",
    "        for iter_id, mini_batch in enumerate(mini_batches):\n",
    "\n",
    "- **外层循环：** 定义遍历数据集的次数，通过参数EPOCH_NUM设置。\n",
    "\n",
    "        for epoch_id in range(EPOCH_NUM):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import paddle\n",
    "from paddle.nn import Linear\n",
    "import paddle.nn.functional as F\n",
    "import numpy as np\n",
    "import os\n",
    "import random\n",
    "\n",
    "def load_data():\n",
    "    # 从文件导入数据\n",
    "    datafile = './datasets/housing.data'\n",
    "    data = np.fromfile(datafile, sep=' ', dtype=np.float32)\n",
    "\n",
    "    # 每条数据包括14项，其中前面13项是影响因素，第14项是相应的房屋价格中位数\n",
    "    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]\n",
    "    feature_num = len(feature_names)\n",
    "\n",
    "    # 将原始数据进行Reshape，变成[N, 14]这样的形状\n",
    "    data = data.reshape([data.shape[0] // feature_num, feature_num])\n",
    "\n",
    "    # 将原数据集拆分成训练集和测试集\n",
    "    # 这里使用80%的数据做训练，20%的数据做测试\n",
    "    # 测试集和训练集必须是没有交集的\n",
    "    ratio = 0.8\n",
    "    offset = int(data.shape[0] * ratio)\n",
    "    training_data = data[:offset]\n",
    "\n",
    "    # 计算train数据集的最大值，最小值，平均值\n",
    "    maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0),                                  training_data.sum(axis=0) / training_data.shape[0]\n",
    "\n",
    "    # 记录数据的归一化参数，在预测时对数据做归一化\n",
    "    global max_values\n",
    "    global min_values\n",
    "    global avg_values\n",
    "    max_values = maximums\n",
    "    min_values = minimums\n",
    "    avg_values = avgs\n",
    "\n",
    "    # 对数据进行归一化处理\n",
    "    for i in range(feature_num):\n",
    "        data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])\n",
    "\n",
    "    # 训练集和测试集的划分比例\n",
    "    training_data = data[:offset]\n",
    "    test_data = data[offset:]\n",
    "    return training_data, test_data\n",
    "\n",
    "\n",
    "class Regressor(paddle.nn.Layer):\n",
    "    # self代表类的实例自身\n",
    "    def __init__(self):\n",
    "        # 初始化父类中的一些参数\n",
    "        super(Regressor, self).__init__()\n",
    "\n",
    "        # 定义一层全连接层，输入维度是13，输出维度是1\n",
    "        self.fc = Linear(in_features=13, out_features=1)\n",
    "\n",
    "    # 网络的前向计算\n",
    "    def forward(self, inputs):\n",
    "        x = self.fc(inputs)\n",
    "        return x\n",
    "\n",
    "\n",
    "def train():\n",
    "    # 声明定义好的线性回归模型\n",
    "    model = Regressor()\n",
    "    # 开启模型训练模式\n",
    "    model.train()\n",
    "    # 加载数据\n",
    "    training_data, test_data = load_data()\n",
    "    # 定义优化算法，使用随机梯度下降SGD\n",
    "    # 学习率设置为0.01\n",
    "    opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters())\n",
    "\n",
    "    EPOCH_NUM = 10   # 设置外层循环次数\n",
    "    BATCH_SIZE = 10  # 设置batch大小\n",
    "\n",
    "    # 定义外层循环\n",
    "    for epoch_id in range(EPOCH_NUM):\n",
    "        # 在每轮迭代开始之前，将训练数据的顺序随机的打乱\n",
    "        np.random.shuffle(training_data)\n",
    "        # 将训练数据进行拆分，每个batch包含10条数据\n",
    "        mini_batches = [training_data[k:k+BATCH_SIZE] for k in range(0, len(training_data), BATCH_SIZE)]\n",
    "        # 定义内层循环\n",
    "        for iter_id, mini_batch in enumerate(mini_batches):\n",
    "            x = np.array(mini_batch[:, :-1]) # 获得当前批次训练数据\n",
    "            y = np.array(mini_batch[:, -1:]) # 获得当前批次训练标签（真实房价）\n",
    "            # 将numpy数据转为飞桨动态图tensor形式\n",
    "            house_features = paddle.to_tensor(x)\n",
    "            prices = paddle.to_tensor(y)\n",
    "\n",
    "            # 前向计算\n",
    "            predicts = model(house_features)\n",
    "\n",
    "            # 计算损失\n",
    "            loss = F.square_error_cost(predicts, label=prices)\n",
    "            avg_loss = paddle.mean(loss)\n",
    "            if iter_id%20==0:\n",
    "                print(\"epoch: {}, iter: {}, loss is: {}\".format(epoch_id, iter_id, avg_loss.numpy()))\n",
    "\n",
    "            # 反向传播\n",
    "            avg_loss.backward()\n",
    "            # 最小化loss,更新参数\n",
    "            opt.step()\n",
    "            # 清除梯度\n",
    "            opt.clear_grad()\n",
    "\n",
    "    # 保存模型参数，文件名为LR_model.pdparams\n",
    "    paddle.save(model.state_dict(), 'LR_model.pdparams')\n",
    "    print(\"模型保存成功，模型参数保存在LR_model.pdparams中\")\n",
    "\n",
    "\n",
    "def load_one_example():\n",
    "    training_data, test_data = load_data()\n",
    "    # 从上边已加载的测试集中，随机选择一条作为测试数据\n",
    "    idx = np.random.randint(0, test_data.shape[0])\n",
    "    idx = -10\n",
    "    one_data, label = test_data[idx, :-1], test_data[idx, -1]\n",
    "    # 修改该条数据shape为[1,13]\n",
    "    one_data =  one_data.reshape([1,-1])\n",
    "\n",
    "    return one_data, label\n",
    "\n",
    "def validation():\n",
    "    model = Regressor()\n",
    "    # 参数为保存模型参数的文件地址\n",
    "    model_dict = paddle.load('LR_model.pdparams')\n",
    "    model.load_dict(model_dict)\n",
    "    model.eval()\n",
    "\n",
    "    # 参数为数据集的文件地址\n",
    "    one_data, label = load_one_example()\n",
    "    # 将数据转为动态图的variable格式\n",
    "    one_data = paddle.to_tensor(one_data)\n",
    "    predict = model(one_data)\n",
    "\n",
    "    # 对结果做反归一化处理\n",
    "    predict = predict * (max_values[-1] - min_values[-1]) + avg_values[-1]\n",
    "    # 对label数据做反归一化处理\n",
    "    label = label * (max_values[-1] - min_values[-1]) + avg_values[-1]\n",
    "\n",
    "    print(\"Inference result is {}, the corresponding label is {}\".format(predict.numpy(), label))\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    train()\n",
    "    validation()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "import torch.nn.functional as F\n",
    "import numpy as np\n",
    "import os\n",
    "import random\n",
    "\n",
    "def load_data():\n",
    "    # 从文件导入数据\n",
    "    datafile = './datasets/housing.data'\n",
    "    data = np.fromfile(datafile, sep=' ', dtype=np.float32)\n",
    "\n",
    "    # 每条数据包括14项，其中前面13项是影响因素，第14项是相应的房屋价格中位数\n",
    "    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]\n",
    "    feature_num = len(feature_names)\n",
    "\n",
    "    # 将原始数据进行Reshape\n",
    "    data = data.reshape([data.shape[0] // feature_num, feature_num])\n",
    "\n",
    "    # 划分训练集和测试集\n",
    "    ratio = 0.8\n",
    "    offset = int(data.shape[0] * ratio)\n",
    "    training_data = data[:offset]\n",
    "\n",
    "    # 计算归一化参数\n",
    "    maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \\\n",
    "                              training_data.sum(axis=0) / training_data.shape[0]\n",
    "\n",
    "    # 保存归一化参数\n",
    "    global max_values, min_values, avg_values\n",
    "    max_values = maximums\n",
    "    min_values = minimums\n",
    "    avg_values = avgs\n",
    "\n",
    "    # 数据归一化\n",
    "    for i in range(feature_num):\n",
    "        data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])\n",
    "\n",
    "    training_data = data[:offset]\n",
    "    test_data = data[offset:]\n",
    "    return training_data, test_data\n",
    "\n",
    "class Regressor(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Regressor, self).__init__()\n",
    "        # 定义线性层\n",
    "        self.fc = nn.Linear(in_features=13, out_features=1)\n",
    "\n",
    "    def forward(self, x):\n",
    "        return self.fc(x)\n",
    "\n",
    "def train():\n",
    "    # 创建模型\n",
    "    model = Regressor()\n",
    "    model.train()\n",
    "\n",
    "    # 加载数据\n",
    "    training_data, test_data = load_data()\n",
    "\n",
    "    # 定义优化器\n",
    "    optimizer = torch.optim.SGD(model.parameters(), lr=0.01)\n",
    "\n",
    "    EPOCH_NUM = 10\n",
    "    BATCH_SIZE = 10\n",
    "\n",
    "    for epoch_id in range(EPOCH_NUM):\n",
    "        # 打乱训练数据\n",
    "        np.random.shuffle(training_data)\n",
    "        # 批次划分\n",
    "        mini_batches = [training_data[k:k+BATCH_SIZE] for k in range(0, len(training_data), BATCH_SIZE)]\n",
    "\n",
    "        for iter_id, mini_batch in enumerate(mini_batches):\n",
    "            x = np.array(mini_batch[:, :-1])\n",
    "            y = np.array(mini_batch[:, -1:])\n",
    "            # 转换为torch tensor\n",
    "            house_features = torch.FloatTensor(x)\n",
    "            prices = torch.FloatTensor(y)\n",
    "\n",
    "            # 前向传播\n",
    "            predicts = model(house_features)\n",
    "\n",
    "            # 计算损失\n",
    "            loss = F.mse_loss(predicts, prices)\n",
    "\n",
    "            if iter_id % 20 == 0:\n",
    "                print(\"epoch: {}, iter: {}, loss is: {}\".format(epoch_id, iter_id, loss.item()))\n",
    "\n",
    "            # 反向传播\n",
    "            optimizer.zero_grad()\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "\n",
    "    # 保存模型\n",
    "    torch.save(model.state_dict(), 'LR_model.pth')\n",
    "    print(\"模型保存成功，模型参数保存在LR_model.pth中\")\n",
    "\n",
    "def load_one_example():\n",
    "    training_data, test_data = load_data()\n",
    "    idx = np.random.randint(0, test_data.shape[0])\n",
    "    idx = -10\n",
    "    one_data, label = test_data[idx, :-1], test_data[idx, -1]\n",
    "    one_data = one_data.reshape([1, -1])\n",
    "    return one_data, label\n",
    "\n",
    "def validation():\n",
    "    model = Regressor()\n",
    "    # 加载模型参数\n",
    "    model.load_state_dict(torch.load('LR_model.pth'))\n",
    "    model.eval()\n",
    "\n",
    "    one_data, label = load_one_example()\n",
    "    # 转换为torch tensor\n",
    "    one_data = torch.FloatTensor(one_data)\n",
    "\n",
    "    with torch.no_grad():\n",
    "        predict = model(one_data)\n",
    "\n",
    "    # 反归一化处理\n",
    "    predict = predict.numpy() * (max_values[-1] - min_values[-1]) + avg_values[-1]\n",
    "    label = label * (max_values[-1] - min_values[-1]) + avg_values[-1]\n",
    "\n",
    "    print(\"Inference result is {}, the corresponding label is {}\".format(predict[0][0], label))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    train()\n",
    "    validation()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cuda11.6_env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
